/************************************************************
 * Project Name         [Thinking_In_Java]
 * File Name            [SyncBank_V1.java]
 * Creation Date        [02-Jul-2014]
 * 
 * Copyright© ge.y.yang@gmail.com All Rights Reserved
 * 
 * Work hard, play harder, think big and keep fit
 ************************************************************/
package pkg_08_thread.synchronization;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 同步的银行, 使用锁
 * 
 * 为了避免选择没有足够资金的帐户作为转出帐户, 我们应该使用锁对象和条件对象一起对线程进行控制.<br>
 * 一个锁对象可以有一个或多个相关的条件对象.<br>
 * 
 * 
 * 1.当取款线程再次运行之前, 帐户中没有足够的资金, 我们应该等待另一个线程向该帐户注入资金.<br>
 * 但是, 这一线程刚刚获得了对bankLock的排他性访问, 因此别的线程没有进行存款操作的机会.<br>
 * 
 * 2.在例子中如果发现余额不足, 调用sufficientFunds.await()方法, 当前线程被阻塞了, 并放弃了锁.<br>
 * 我们希望这样可以使另外一个线程可以进行存款操作.<br>
 * 
 * 3.等待获得锁的线程和调用await方法的线程存在本质的不同, 一旦一个线程调用await方法, 它进入该条件的等待集<br>
 * 当锁可用时, 该线程并不马上解除阻塞状态, 相反, 它处于阻塞状态直到另一个线程调用同一条件上的signalAll方法为止.<br>
 * 
 * 4.当另一个线程转账时, 它调用sufficientFunds.signalAll()方法, 重新激活因为这一条件而等待的所有线程<br>
 * 
 * 5.当这些线程从等待集中移出时, 它们再次成为可运行的, 调度器将再度激活它们<br>
 * 同时, 它们将试图重新进入该对象. 一旦锁成为可用的, 它们中的某个将从await返回调用, 获得该锁并从被阻塞的地方继续执行.<br>
 * 
 * 
 * @author 不落的太阳(Sean Yang)
 * @version 1.0
 * @since JDK 6
 * 
 */
public class SyncBank_V1 implements Bank {

	private final double[] accounts;
	private Lock bankLock = null;
	private Condition sufficientFunds;

	public SyncBank_V1(int n, double initialBalance) {
		accounts = new double[n];
		for (int i = 0; i < n; i++) {
			accounts[i] = initialBalance;
		}
		// 初始化锁极其条件
		bankLock = new ReentrantLock();
		sufficientFunds = bankLock.newCondition();
	}

	@Override
	public void transfer(int fromAccount, int toAccount, double amount) {
		bankLock.lock();

		try {
			while (accounts[fromAccount] < amount) {
				// 当余额不足时当前线程进入等待
				sufficientFunds.await();
			}

			System.out.print("Thred ---> " + Thread.currentThread());
			accounts[fromAccount] -= amount;
			System.out.printf(" %10.2f from %d to %d", amount, fromAccount,
					toAccount);
			accounts[toAccount] += amount;
			System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
			// 重新激活因为sufficientFunds而等待的所有线程
			sufficientFunds.signalAll();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			bankLock.unlock();
		}

	}

	public double getTotalBalance() {
		bankLock.lock();
		try {
			double sum = 0;
			for (double d : accounts) {
				sum += d;
			}
			return sum;
		} finally {
			bankLock.unlock();
		}
	}

	@Override
	public int size() {
		return accounts.length;
	}
}
